package image;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;

import deformationPainter.AffinePullBack;
import deformedGrid.RectangelGridDeformation;
import stegru.tools.multiThreat.ParallelFor;

/**
 * 
 * @author Stephan
 *
 */
public class GridBasedImageDeformation extends RectangelGridDeformation{


	private final BufferedImage image;
	public Color bgColor = Color.BLACK;
	public GridBasedImageDeformation(BufferedImage image) {
		super(image.getWidth()/100,  image.getHeight()/100, image.getWidth()-1, image.getHeight()-1, false);
		this.image = image;
	}

	
	private Color getsrcColor(double x, double y){
//		x = x <=image.getWidth()-1 ? x : image.getWidth()-1;
//		x = x >=0 ? x : 0;
//		y = y <=image.getHeight()-1 ? y : image.getHeight()-1;
//		y = y >=0 ? y : 0;
		if (x >=image.getWidth() -1 || x<0 ||y >=image.getHeight() -1 || y<0 ) {
			return null;
		}
		int x1 =(int) Math.floor(x);
		int y1 = (int) Math.floor(y);
		int x2  =(int)Math.ceil(x);
		int y2 = (int) Math.ceil(y);
		x-= x1;
		y-= y1;
		//System.out.println(" " +x1 +" " +x2 +" " +y1 +" " +y2 );
		int ul = image.getRGB(x1, y1);
		int ur = image.getRGB(x2, y1);
		int dl = image.getRGB(x1, y2);
		int dr = image.getRGB(x2, y2);	
		int red =(int)
				(((ul >> 16) & 0x000000FF)*(1-x)*(1-y)+
				((dl >> 16) & 0x000000FF)*(x)*(1-y)+
				((ur >> 16) & 0x000000FF)*(1-x)*(y)+
				((dr >> 16) & 0x000000FF)*(x)*(y));
		int green =(int)
				(((ul >> 8) & 0x000000FF)*(1-x)*(1-y)+
				((dl >> 8) & 0x000000FF)*(x)*(1-y)+
				((ur >> 8) & 0x000000FF)*(1-x)*(y)+
				((dr >> 8) & 0x000000FF)*(x)*(y));
		int blue =(int)
				(((ul) & 0x000000FF)*(1-x)*(1-y)+
				((dl) & 0x000000FF)*(x)*(1-y)+
				((ur) & 0x000000FF)*(1-x)*(y)+
				((dr) & 0x000000FF)*(x)*(y));
		red = red <255 ? red : 255;
		red = red >=0 ? red : 0;
		green = green <255 ? green : 255;
		green = green >=0 ? green : 0;
		blue = blue <255 ? blue : 255;
		blue = blue >=0 ? blue : 0;
		return new Color(red, green, blue);
	}
	
	public BufferedImage getImage() { //TODO variable size
		// Memory access pattern 
		
		final int imageSizeY = image.getHeight();
		final int imageSizeX = image.getWidth();
		final BufferedImage off_Image =
				  new BufferedImage(imageSizeX, imageSizeY,
				                    BufferedImage.TYPE_INT_RGB);

		final Graphics2D g2 = off_Image.createGraphics();
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		final int numGridPointsX = super.numGridPointsX;
		final int numGridPointsY = super.numGridPointsY;
		final double[][] changedGridPoints = getGrid().toArray();
		final double[][] gridPoints = getOriginalGrid().toArray();
		final double[][][] normalRow = new double[numGridPointsX - 1][numGridPointsY][3];
		final double[][][] normalColumn = new double[numGridPointsX][numGridPointsY - 1][3];
		// calculate normalRow
		fillNormalRow(normalRow, numGridPointsX, numGridPointsY,
				changedGridPoints);

		// calculate normalColumn
		fillNormalColumn(normalColumn, numGridPointsX, numGridPointsY,
				changedGridPoints);

		// Drawing
		(new ParallelFor(0, imageSizeY - 1) {
			@Override
			protected void loop(int y) {
				int gridBoxX = 0;
				int gridBoxY = -1;
				// sucht erste box in der y liegt
				double[] p = new double[2];
				p[1]=y;/// (imageSizeY - 1.0);
				while (changedGridPoints[numGridPointsX
						* (++gridBoxY + 1)][1] <= p[1]) {
					if (gridBoxY == numGridPointsY - 2) { // wenn y in allen
														// davor nicht war
														// muss es in der
														// letzten sein
						break;
					}
				}
				for (int x = 0; x < imageSizeX; x++) {
					p[0] = x;/// (imageSizeX - 1.0);

					boolean changed;
					boolean fail = false;
					int safeCounter = 0;
					do {
						if (++safeCounter > 1000) {
							StringBuilder builder = new StringBuilder();
							builder.append("BREAK, infinit loop at box search, maybe not convex box (");
							builder.append("x=");
							builder.append(x);
							builder.append(", y=");
							builder.append(y);
							builder.append(")");
							System.err.println(builder.toString());
							fail = true;
							break;
						}
						changed = false;
						/**
						 * p1 ----> p2
						 *  |	    |
						 *  |		|
						 *  \/ 		\/
						 *   p4 ----> p3
						 */
						
						// up
						if (normalRow[gridBoxX][gridBoxY][0] == normalRow[gridBoxX][gridBoxY][0]) { // is
																									// not
																									// NaN
							if (p[0] * normalRow[gridBoxX][gridBoxY][1] + p[1]
									* normalRow[gridBoxX][gridBoxY][2] > normalRow[gridBoxX][gridBoxY][0]) { // true
																												// if
																												// not
																												// in
																												// box
								gridBoxY--;
								changed = true;
							}
						}
						// right
						if (normalColumn[gridBoxX + 1][gridBoxY][0] == normalColumn[gridBoxX + 1][gridBoxY][0]
								&& !changed) { // is not NaN
							if (p[0] * normalColumn[gridBoxX + 1][gridBoxY][1]
									+ p[1]
									* normalColumn[gridBoxX + 1][gridBoxY][2] > normalColumn[gridBoxX + 1][gridBoxY][0]) { // true
																															// if
																															// not
																															// in
																															// box
								gridBoxX++;
								changed = true;
							}
						}
						// down
						if (normalRow[gridBoxX][gridBoxY + 1][0] == normalRow[gridBoxX][gridBoxY + 1][0]
								&& !changed) { // is not NaN
							if (p[0] * normalRow[gridBoxX][gridBoxY + 1][1]
									+ p[1]
									* normalRow[gridBoxX][gridBoxY + 1][2] < normalRow[gridBoxX][gridBoxY + 1][0]) { // true
																														// if
																														// not
																														// in
																														// box
								gridBoxY++;
								changed = true;
							}
						}
						// left
						if (normalColumn[gridBoxX][gridBoxY][0] == normalColumn[gridBoxX][gridBoxY][0]
								&& !changed) { // is not NaN
							if (p[0] * normalColumn[gridBoxX][gridBoxY][1]
									+ p[1]
									* normalColumn[gridBoxX][gridBoxY][2] < normalColumn[gridBoxX][gridBoxY][0]) { // true
																													// if
																													// not
																													// in
																													// box
								gridBoxX--;
								changed = true;
							}
						}
					} while (changed);
					
					double[][] newBox = new double[4][];
					newBox[0] = changedGridPoints[gridBoxX + numGridPointsX
							* (gridBoxY)];
					newBox[1] = changedGridPoints[gridBoxX + 1 + numGridPointsX
							* (gridBoxY)];
					newBox[2] = changedGridPoints[gridBoxX + 1 + numGridPointsX
							* (gridBoxY + 1)];
					newBox[3] = changedGridPoints[gridBoxX + numGridPointsX
							* (gridBoxY + 1)];
					double[][] oldBox = new double[4][];
					oldBox[0] = gridPoints[gridBoxX + numGridPointsX * (gridBoxY)];
					oldBox[1] = gridPoints[gridBoxX + 1 + numGridPointsX
							* (gridBoxY)];
					oldBox[2] = gridPoints[gridBoxX + 1 + numGridPointsX
							* (gridBoxY + 1)];
					oldBox[3] = gridPoints[gridBoxX + numGridPointsX
							* (gridBoxY + 1)];
					double[] f = AffinePullBack.calculate(p,  newBox, oldBox);
					synchronized (g2) {
						//	System.out.println(x +" "+ y);
						//	System.out.println(gridBoxX +" "+ gridBoxY );
						Color c = getsrcColor(f[0], f[1]);
						if (!fail && c != null) {
							g2.setColor(c);
							g2.fillRect(x, y, 1, 1);
						}
						}
				}

			}
		}).calculate();
		off_Image.flush();
		System.out.println("drawed");
		return off_Image;
	}


	private void fillNormalColumn(final double[][][] normalColumn,
			final int numGridPointsX, final int numGridPointsY,
			final double[][] changedGridPoints) {
		(new ParallelFor(0, numGridPointsX - 1) {
			@Override
			protected void loop(int x) {
				if (x == 0 || x == numGridPointsX - 1) {
					for (int y = 0; y < numGridPointsY - 1; y++) {
						normalColumn[x][y][0] = Double.NaN;
					}
				} else {
					for (int y = 0; y < numGridPointsY - 1; y++) {
						double n1 = changedGridPoints[x + numGridPointsX * (y + 1)][1]
								- changedGridPoints[x + numGridPointsX * y][1];
						double n2 = changedGridPoints[x + numGridPointsX * y][0]
								- changedGridPoints[x + numGridPointsX * (y + 1)][0];
						normalColumn[x][y][1] = n1;
						normalColumn[x][y][2] = n2;
						normalColumn[x][y][0] = n1
								* changedGridPoints[x + numGridPointsX * y][0] + n2
								* changedGridPoints[x + numGridPointsX * y][1];
					}
				}
			}
		}).calculate();
	}


	private void fillNormalRow(final double[][][] normalRow,
			final int numGridPointsX, final int numGridPointsY,
			final double[][] changedGridPoints) {
		(new ParallelFor(0, numGridPointsY - 1) {
			@Override
			protected void loop(int y) {
				if (y == 0 || y == numGridPointsY - 1) {
					for (int x = 0; x < numGridPointsX - 1; x++) {
						normalRow[x][y][0] = Double.NaN;
					}
				} else {
					for (int x = 0; x < numGridPointsX - 1; x++) {
						double n1 = changedGridPoints[x + 1 + numGridPointsX * y][1]
								- changedGridPoints[x + numGridPointsX * y][1];
						double n2 = changedGridPoints[x + numGridPointsX * y][0]
								- changedGridPoints[x + 1 + numGridPointsX * y][0];
						normalRow[x][y][1] = n1;
						normalRow[x][y][2] = n2;
						normalRow[x][y][0] = n1
								* changedGridPoints[x + numGridPointsX * y][0] + n2
								* changedGridPoints[x + numGridPointsX * y][1];
					}
				}
			}
		}).calculate();
	}



}
